/**
 * sybn version: 20250731
 */
var def_username;
var token;
// SQL语句高亮
//$(document).ready(function() {
window.onload = function() {
    var p_dbsource = getQueryString("dbsource", "");
    var p_dbsource_all = getQueryString("dbsource_all", "");

    // 初始化sql编辑器
    initSqlEditor({});
    // 初始化sql编辑器的代码提示
    initSqlEditorByApi("api/bean/table_info.json?is_subset=true&_res_type=mini&select_type=can_show_create_table&dbsource=" + $('.dbSource').val());
    // 显示版本信息
    showVersion('api/job_v2/version.json', $('.title'))
    // 显示数据源
    showSelect('api/bean/dbsource.json?dbsource_all=' + p_dbsource_all, $('.dbSource'), p_dbsource, true, function(data){$('.dbSource').selectpicker({ width: '350px' });});

    $('.CodeMirror').addClass("form-control");

    // 当 dbSource 被点击时, 将 dbSource 写入地址栏
    $(".dbSource").on("click", function(e) {
        var url2 = changeURLPar(window.location.href.toString(), "dbsource", $('.dbSource').val());
        window.history.pushState({},0,url2);

        var tableInfoUrl = "api/bean/table_info.json?is_subset=true&_res_type=mini&select_type=can_show_create_table&dbsource=" + $('.dbSource').val();
        if (e.altKey) {
            console.log("按下了 alt 键开始强制更新表结构,可能会慢一点...");
            tableInfoUrl = "api/bean/table_info.json?is_subset=true&_res_type=mini&select_type=show_create_table&dbsource=" + $('.dbSource').val();
        }
        // 初始化sql编辑器的代码提示
        initSqlEditorByApi(tableInfoUrl);
    });
    $('.dbSource').on('changed.bs.select', function(e, clickedIndex, isSelected, previousValue) {
        var url2 = changeURLPar(window.location.href.toString(), "dbsource", $(this).val());
        window.history.pushState({},0,url2);

        // 初始化sql编辑器的代码提示
        var tableInfoUrl = "api/bean/table_info.json?is_subset=true&_res_type=mini&select_type=can_show_create_table&dbsource=" + $('.dbSource').val();
        initSqlEditorByApi(tableInfoUrl);
    });

    // 登陆相关
    def_username = $.cookie('def_username');
    token = $.cookie('token');
    if (!def_username || !token) {
        $('#loginModal').modal({show:true,backdrop:'static',keyboard:false});
        $('.login_close').hide();
    } else {
        $('#loginModal').modal('hide');
    }

    // 登陆
    $(".login_submit").on("click", function() {
        loginFrom("api/user_core/login.json", function(data){
            if (data && data.username) {
                $.cookie('def_username', data.username, { expires: 28 });
                if (data.token) {
                    $.cookie('token', data.token, { expires: 28 });
                }
                $(".login_username").text(": "+ data.username);
                def_username = data.username;
                var sql = "SELECT platform, create_time, params FROM editor_action_log where user = '"+def_username+"'  and params != 'show databases'  order by id desc LIMIT 10";
                var sql_editor = window[$('.stream_sql_input').data("editor")];
                sql_editor.setValue(sql);
            }
        })
    });
    // 注销
    $(".login_out").on("click", function() {
        $(".login_username").text("");
        $.cookie('def_username', '', { expires: 0 });
        $.cookie('token', '', { expires: 0 });
        loginOut("api/user_core/logout.json", function(data){
            $('#loginModal').modal({show:true,backdrop:'static',keyboard:false});
            $('#loginModal .prompt').html('<span></span>');
            $('.login_close').hide();
        })
    });

    // 中止正在执行的任务
    $('body').on('click', '.interrupt', function(e) {
        var api = "api/job_v2/interrupt";
        var apiParam = $(this).data("param") || "";
        var _this = this, $this = $(this);
        var $input_group = $this.closest(".input-group");
        var statusKey = $input_group.data("statusKey");
        if (statusKey) {
            openApi(_this, e, api + ".json?statusKey=" + statusKey + "&user=" + def_username + apiParam);
        }
    });

    // 将当前算元格的值写入sql中, 比如  id = 1. 默认写入最后一个 from 或 where 后面.
    // TODO 暂时无法正确识别 from (select * from x where a = 1)
    $('body').on('click', '.sql_append', function(e) {
        var sql_editor = window[$(this).data("editor_id")];
        var sql = sql_editor.getValue();

        // 生成需要插入的 sql
        var field = $(this).data("field");
        var type = $(this).data("type");
        var valueObj = $(this).data("value");
        var value = type == "number" ? valueObj + "" : "'" + valueObj + "'"
        var op = $(this).data("op");
        if (op == "in") {
            value = "(" + value + ")";
        } else if (op == "like") {
            value = "'%" + valueObj + "%'";
        }
        var appendSql = " " + field + " " + op  + " " + value;


        // 当前选中内容
        var selection = sql_editor.getSelection();
        // 光标位置: {line: 1, ch: 32, sticky: 'before'} sticky 表示当前选中内容在光标的前还是后
        var cur = sql_editor.getCursor();

        if (cur && cur.sticky && cur.line + cur.ch > 0) {
            // 从光标选中的位置添加或替换内容



            // 获取光标前的字符串
            var preSql = sql_editor.getLine(cur.line).substr(0, cur.ch - (cur.sticky == "before" ? selection.length : 0)).replace(/\s/g, ' ').toUpperCase().replace(/\s+$/, ' ');
            // 获取光标后的字符串
            var postSql = sql_editor.getLine(cur.line).substr(cur.ch + (cur.sticky == "before" ? 0 : selection.length)).replace(/\s/g, ' ').toUpperCase().replace(/\s+$/, ' ');

            // 判断光标前面是否是 and or
            var preSqlTrim = preSql.trim().replaceAll(/\s/, " ") + " ";
            var preEndAndOr = preSqlTrim.endsWith(" and ") || preSqlTrim.endsWith(" or ");
            // 判断光标后面是否是 and or
            var postSqlTrim = " " + postSql.trim().replaceAll(/\s/, " ");
            var postStartAndOr = postSqlTrim.startsWith(" and ") || postSqlTrim.startsWith(" or ");

            // 获取光标选中的字符串
            var selection2 = selection.trim().toUpperCase().replaceAll(/\s/, " ");

            if (selection2.startsWith("AND ")) {
                appendSql = " AND" + appendSql;
            } else if (selection2.startsWith("OR ")) {
                appendSql = " OR" + appendSql;
            } else if (selection2.startsWith("NOT ")) {
                appendSql = " NOT" + appendSql;
            } else if (selection2.startsWith("WHERE ")) {
                appendSql = " WHERE" + appendSql;
            } else if (selection2.startsWith("HAVING ")) {
                appendSql = " HAVING" + appendSql;
            } else if (selection2.startsWith("GROUP BY ") ) {
                if (selection2.indexOf(" HAVING ") < 0) {
                    appendSql = selection + " \r\nHAVING " + appendSql;
                } else {
                    appendSql = selection + " AND " + appendSql;
                }
            } else if (selection2.startsWith("FROM ")) {
                if (selection2.indexOf(" WHERE ") < 0 && selection2.indexOf(" HAVING ") < 0) {
                    appendSql = selection + " \r\nWHERE " + appendSql;
                } else {
                    appendSql = selection + " AND " + appendSql;
                }
            } else if (selection2.startsWith(">") || selection2.startsWith("<") || selection2.startsWith("=")  || selection2.startsWith("IN ") || selection2.startsWith("LIKE ")) {
                appendSql = " " +  op  + " " + value;
            }
            if (!preStr.endsWith(" WHERE ") && preStr != "WHERE " && !preStr.endsWith(" HAVING ") && preStr != "HAVING " && !preEndAndOr){
                appendSql = "AND " + appendSql;
            }
            sql_editor.replaceSelection(appendSql + " ");
        } else {
            // 没有光标, 则寻找 where 等字符, 从而计算插入 sql 的位置

            var sql2 = sql.toLowerCase().replaceAll(/\s/, " ");
            var idx = -1;
            var idxFrom = sql2.lastIndexOf(" from ");
    //        if (idxFrom > 0) {
    //             idx = idx + 6;
    //        }
            var idxWhere = sql2.lastIndexOf(" where ");
            if (idxWhere > 0 && idxWhere > idxFrom) {
                 idx = idxWhere + 7;
            }
            var idxGroup = sql2.lastIndexOf(" group ");
            var idxOrder = sql2.lastIndexOf(" order ");
            var idxSkip = sql2.lastIndexOf(" skip ");
            var idxLimit = sql2.lastIndexOf(" limit ");
            var min = minIdx(idxOrder, idxGroup);
            min = minIdx(min, idxOrder);
            min = minIdx(min, idxLimit);
            if (min > idx) {
                idx = min;
            }
            if (idx <= 0) {
                idx = sql2.length;
            }

            // 生成新的 sql 并插入

            var preSql = sql2.substring(0, idx);
            var postSql = sql2.substring(idx);

            // 判断光标前面是否是 and or
            var preSqlTrim = preSql.trim() + " ";
            var preEndAndOr = preSqlTrim.endsWith(" and ") || preSqlTrim.endsWith(" or ");
            // 判断光标后面是否是 and or
            var postSqlTrim = " " + postSql.trim();
            var postStartAndOr = postSqlTrim.startsWith(" and ") || postSqlTrim.startsWith(" or ");

            if (idxWhere + 7 == idx && idxWhere > idxFrom && (idx == sql2.length || sql.substring(idx).trim().startsWith(")"))) {
                // 光标前面是 where 且光标后面没有内容
                appendSql = appendSql;
            } else if (idxWhere + 7 == idx && idxWhere > idxFrom && (idx == idxGroup || idx == idxOrder || idx == idxSkip || idx == idxLimit)) {
                // 光标前面是 where 且光标后面没有内容
                appendSql = appendSql;
            } else if (idxWhere + 7 == idx && idxWhere > idxFrom && !postStartAndOr) {
                // 光标前面是 where 且光标后面有不是 and or 内容
                appendSql = appendSql + " AND ";
            } else if (idxWhere > idxFrom && !preEndAndOr) {
                // 光标前面不是 where and or 则加 and 前缀
                appendSql = " AND" + appendSql ;
            } else {
                // 光标则增加 where 前缀
                appendSql = " WHERE" + appendSql;
            }

            var newSql = sql.substring(0, idx) + appendSql + sql.substring(idx);
            sql_editor.setValue(newSql);
        }
    });

    // 自动注释sql
    $('body').on('click', '.commentSql', function(e) {
        var $input_group =$(this).closest(".input-group, .sybn-api-demo")
        var sql_editor = window[$input_group.find('[name=url],[name=sql]').data("editor")];
        var sql = sql_editor.getValue();
        var dbSource = $('.dbSource').find('option:selected').val() || $('.dbSource').val() || "";
        $.ajax({
            url: "api/sql/translate.json", type:'post', dataType:'json', async:true,
            data: { query: sql, pretty: false, dbSource:dbSource, outputType: 'commentSql'},
            //执行成功的回调函数
            success: function(data) {
                if (data && data.result && data.output && data.output != sql.trim()) {
                    sql_editor.setValue(data.output)
                }
            },
            //执行失败或错误的回调函数
            error: function(data) {
                alert("失败");
            }
        });
    });

    // 监听 sybnSqlEngine 的变化事件
    $('body').on('change', '[name=sybnSqlEngine]', function() {
        var $this = $(this);
        var selectedValue = $this.val(); // 获取当前选中的值
        var $input_group = $this.closest(".input-group, .sybn-api-demo");
        var sql_editor = window[$input_group.find('[name=url],[name=sql]').data("editor")]; // 获取对应的 sql_editor

        if (!sql_editor) return; // 如果没有找到编辑器，则直接返回

        if (selectedValue === "javascript") {
            // 如果新值是 javascript，则将样式设为 text/javascript
            sql_editor.setOption("mode", "text/javascript");
        } else {
            // 如果旧值是 javascript，且新值不是，则改回默认值 text/sysql
            var currentMode = sql_editor.getOption("mode");
            if (currentMode === "text/javascript") {
                sql_editor.setOption("mode", "text/sysql");
            }
        }
    });

    $('.CodeMirror').bind('keydown', 'Shift+Enter', function(event){
        if(event.keyCode == 13 && event.shiftKey){
            // Shift+Enter 提交当前sql
            event.preventDefault();
            sqlFun(event);
        } else if(event.keyCode == 188 && event.ctrlKey){
            event.preventDefault();
            var $this = $(this);
            if (!$this.hasClass("btn")) {
                $this = $(document.activeElement).closest(".panel").find(".defaults_action");
            }
            var $input_group = $this.closest(".input-group, .sybn-api-demo");
            var sql_editor = window[$input_group.find('[name=url],[name=sql]').data("editor")];
            var sql = sql_editor.getValue();
            var selection = sql_editor.getSelection();

            // 处理选中的SQL部分
            if (selection) {
                processUnionSql(selection, sql_editor);
            }
        } else if(event.keyCode == 191 && event.ctrlKey){
            // Ctrl+/ 注释或反注释选中代码
            event.preventDefault();

            var $this = $(this), _this = this;
            if (!$this.hasClass("btn")) {
                // 获取当前焦点同级的 defaults_action
                $this = $(document.activeElement).closest(".panel").find(".defaults_action");
            }
            var sql_editor = window[$this.closest(".input-group, .sybn-api-demo").find('[name=url],[name=sql]').data("editor")];
            var sql = sql_editor.getValue();

            var selection = sql_editor.getSelection();
            if (selection) {
                // 替换选中内容
                var selection2 = selection.trim()
                if (selection2.startsWith("/*") && selection2.endsWith("*/") ) {
                    selection = selection2.replace(/^\/\*+\s(\s*[\s\S]*?)\s*\*+\//, "$1");
                } else if (selection2.startsWith("-- ")) {
                    selection = selection2.substr(3).trim();
                } else if (selection2.startsWith("# ")) {
                    selection = selection2.substr(2).trim();
                } else {
                    selection = "/* " + selection + "*/";
                }
                sql_editor.replaceSelection(selection)
            } else {
                // 替换当前行
                var cur = sql_editor.getCursor();
                if (cur) {
                    var lines = sql.replaceAll(/\r?\n/, "\n").split("\n");
                    var selection = lines[cur.line];
                    var selection2 = selection.trim()
                    if (selection2.startsWith("/*") && selection2.endsWith("*/") ) {
                        selection = selection2.replace(/^\/\*+\s*([\s\S]*?)\s*\*+\//, "$1");
                    } else if (selection2.startsWith("-- ")) {
                        selection = selection2.substr(3).trim();
                    } else if (selection2.startsWith("# ")) {
                        selection = selection2.substr(2).trim();
                    } else {
                        selection = "/* " + selection + "*/";
                    }
                    var cur_line = cur.line;
                    lines[cur.line] = selection;
                    sql_editor.setValue(lines.join("\n"));
                    cur = sql_editor.getCursor();
                    cur.line = cur_line;
                    sql_editor.setCursor(cur)
                    sql_editor.refresh();
                 }
            }
            return false;
        };
    });

    // 当前窗口 查询/下载
    $('body').on('click', '.sqlApi', sqlFun);
    // 新窗口 查询/下载
    $('body').on('dblclick', '.sqlApi', sqlDownloadFun);

    // 从地址栏获取sql
    var sql = getQueryString("sql");
    var sqlZip = getQueryString("zip");
    if (sqlZip) {
        sql=unzip(sqlZip);
    }
    if (sql) {
        var sql_editor = window[$('[name=url],[name=sql]').last().data("editor")];
        sql_editor.setValue(sql);
    }
};

// 处理 union 字段差异
function processUnionSql(sql, sqlEditor) {
    // union 切分
    const queries = splitUnionQueries(sql);
    // 获取字段列表
    const fieldLists = queries.map(query => extractFieldsFromQuery(query));

    // 补齐字段并生成新 SQL
    const alignedQueries = alignQueriesWithNullFields(queries, fieldLists);

    // 按照 UNION ALL 结构重新拼接
    const resultSql = alignedQueries.join('\nUNION ');

    // 将结果写回编辑器
    sqlEditor.setValue(resultSql);
}

// 补齐字段并生成新的 SQL
function alignQueriesWithNullFields(queries, fieldLists) {
    // 获取所有唯一字段别名，并排序以保证字段顺序一致
    const allFields = new Set();

    // 收集所有字段别名并排序（统一顺序）
    fieldLists.forEach(fields => {
        if (Array.isArray(fields)) {
            fields.forEach(f => allFields.add(f));
        } else {
            Object.keys(fields).forEach(f => allFields.add(f));
        }
    });

    // 统一字段顺序：按字母升序排列
    const sortedAllFields = [...allFields].sort();

    return queries.map((query, index) => {
        const currentFields = fieldLists[index];

        // 判断是新格式还是旧格式
        let currentAliases = [];
        if (Array.isArray(currentFields)) {
            currentAliases = currentFields;
        } else {
            currentAliases = Object.keys(currentFields);
        }

        const missingFields = sortedAllFields.filter(f => !currentAliases.includes(f));

        if (missingFields.length === 0 && currentAliases.length === sortedAllFields.length) {
            // 如果字段数量和顺序都一致，直接返回原 query
            return query;
        }

        // 构建 SELECT 子句，按照统一顺序拼接字段
        const selectClauseParts = [];

        // 如果是新格式，保留原始字段表达式，并按统一顺序拼接
        if (!Array.isArray(currentFields)) {
            for (const field of sortedAllFields) {
                if (currentAliases.includes(field)) {
                    selectClauseParts.push(currentFields[field]);
                } else {
                    selectClauseParts.push(`NULL AS ${field}`);
                }
            }
        } else {
            // 旧格式兼容：使用 NULL AS 字段
            for (const field of sortedAllFields) {
                if (currentAliases.includes(field)) {
                    continue; // 已存在，跳过
                }
                selectClauseParts.push(`NULL AS ${field}`);
            }

            // 拼接原有字段（需保留原始字段表达式？可选）
            // 若你希望保留原始字段表达式，请从 originalQuery 提取后再处理
            selectClauseParts.unshift(...currentAliases.map(f => `NULL AS ${f}`));
        }

        const selectClause = selectClauseParts.join(', ');

        // 替换原始 SELECT 部分
        const selectRegex = /select\s+(.*?)\s+from/si;
        const match = query.match(selectRegex);
        if (!match) return query;

        return query.replace(selectRegex, `SELECT ${selectClause}\r\nFROM`);
    });
}

// 分割 SQL 子查询
function splitUnionQueries(sql) {
    const unionRegex = /\bunion\b/gi;
    const parts = [];
    let lastIndex = 0;

    let match;
    while ((match = unionRegex.exec(sql)) !== null) {
        parts.push(sql.slice(lastIndex, match.index).trim());
        lastIndex = match.index + match[0].length;
    }

    // 添加最后一个子查询
    if (lastIndex < sql.length) {
        parts.push(sql.slice(lastIndex).trim());
    }

    return parts.filter(part => part.length > 0);
}

// 提取并规范化字段列表，返回字段别名 -> 原始字段表达式的对象
function extractFieldsFromQuery(query) {
    const selectRegex = /select\s+(.*?)\s+from/si;
    const match = query.match(selectRegex);

    if (!match) return {};

    const selectClause = match[1].trim();
    const fields = {}; // 存储字段别名 -> 原始字段表达式

    let currentField = '';
    let inQuote = false;
    let quoteChar = null; // 保存当前引号类型（单引号或双引号）
    let inParentheses = 0;

    for (let i = 0; i < selectClause.length; i++) {
        const char = selectClause[i];

        if ((char === "'" || char === '"' || char === '`') && (i === 0 || selectClause[i - 1] !== '\\')) {
            if (inQuote && char === quoteChar) {
                inQuote = false;
                quoteChar = null;
            } else if (!inQuote) {
                inQuote = true;
                quoteChar = char;
            }
            currentField += char;
            continue;
        }

        if (!inQuote && char === '(') {
            inParentheses++;
        } else if (!inQuote && char === ')') {
            inParentheses--;
        }

        if (!inQuote && char === ',' && inParentheses === 0) {
            if (currentField.trim()) {
                const normalized = normalizeFieldName(currentField.trim());
                fields[normalized] = currentField.trim(); // 存储原始字段表达式
            }
            currentField = '';
            continue;
        }

        currentField += char;
    }

    if (currentField.trim()) {
        const normalized = normalizeFieldName(currentField.trim());
        fields[normalized] = currentField.trim();
    }

    return fields;
}

// 比较字段列表差异
function compareFieldLists(fieldLists) {
    const allFields = new Set();
    fieldLists.forEach(fields => {
        fields.forEach(field => allFields.add(field));
    });

    const commonFields = [...allFields];
    const differences = [];

    fieldLists.forEach((fields, index) => {
        const missingFields = commonFields.filter(field => !fields.includes(field));
        if (missingFields.length > 0) {
            differences.push({ queryIndex: index + 1, missingFields });
        }
    });

    return differences;
}

// 去除别名前缀和空格
function normalizeFieldName(field) {
    // 匹配 AS 关键字后的别名（支持不区分大小写）
    const asRegex = /\bas\s+([^"'\s]+)|\bas\s+(['"`])(.+?)\2/gi.exec(field);
    if (asRegex && (asRegex[1] || asRegex[3])) {
        return (asRegex[1] || asRegex[3]).trim().replace(/^["'`]|["'`]$/g, '');
    }

    // 匹配标识符分隔符（如 a.b -> b）
    const dotRegex = /(?:["'`])?[^."'\`]+(?:["'`])?\.(?:["'`])?([^."'\`]+)(?:["'`])?$/;
    const dotMatch = field.match(dotRegex);
    if (dotMatch && dotMatch[1]) {
        return dotMatch[1].trim().replace(/^["'`]|["'`]$/g, '');
    }

    // 如果没有匹配到别名或点符号，则返回最后一个单词作为字段名
    const lastWord = field.split(/\s+/).pop();
    return lastWord.trim().replace(/^["'`]|["'`]$/g, '');
}


// 设置sql
var setSqlFun = function(t) {
    var setSql;
    if (typeof t != 'string' && t.value != undefined) {
       setSql = t.value;
    }
    if (!setSql) {
      return;
    }
    var $input_group = $(t).closest(".input-group, .sybn-api-demo")
    var sql_editor = window[$input_group.find('[name=url],[name=sql]').data("editor")];
    if (sql_editor) {
        sql_editor.setValue(setSql);
    }
}

// 当前窗口 查询/下载
var sqlFun = function(e) {
    // 防止双击触发单击
    clearTimeout(sybn_time);

    var $this = $(this), _this = this;
    if (!$this.hasClass("btn")) {
        // 获取当前焦点同级的 defaults_action
        $this = $(document.activeElement).closest(".panel").find(".defaults_action");
        _this = $this[0];
    }

    // 主接口地址
    var api = $this.data("api") || "api/sql/select_v2";
    // 附加参数
    var apiParam = $this.data("param") || "";

    var $input_group = $this.closest(".input-group, .sybn-api-demo")
    var $panel_body = $this.closest(".sybn-api-demo");
    if ($panel_body.length == 0){
        $panel_body = $this.closest(".input-group");
    }
    var sql_editor = window[$input_group.find('[name=url],[name=sql]').data("editor")];
    var sql = $this.hasClass("selection") && sql_editor.getSelection() ? sql_editor.getSelection() : sql_editor.getValue();
    var sybnSqlEngine = $input_group.find('[name=sybnSqlEngine]').val();
    var sqlMaxLength = $input_group.find('[name=sqlMaxLength]').val();
    if (!sql) {
        return;
    }

    // 如果当前内容可能是 mybatis 代码, 提示是否为变量自动添加 <bind name='...' value='...' />
    var append = appendMybatisBindTags(sql_editor, sql);
    if (append) {
        return;
    }

    if (sql_editor.getSelection() && sql.toUpperCase().indexOf("SELECT") < 0 && sql.toUpperCase().indexOf("SHOW") <0) {
         var choice = window.confirm("当前选中的内容可能不是完整SQL, 是否继续查询当前选中内容? \r\n"  + (sql.length > 256 ? sql.substr(0,256) + "..." : sql));
         if (!choice) {
            return;
         }
    }
    if ((sql.indexOf("　") > 0 || sql.indexOf(" ") > 0)&& confirm("sql中有中文空格,是否替换为普通空格查询？\n\n请确认！")==true){
        sql = sql.replace(/ /g," ").replace(/　/g," ");
        sql_editor.setValue(sql);
    }

    var notSpaceError = notSpaceAfterEvenQuotes(sql);
    if (notSpaceError != '' && confirm("以下位置疑似缺空格, 是否自动补充? \r\n" + notSpaceError)==true){
        console.log("补充空格:\r\n" + notSpaceError);
        var sql2 = sql;
        sql = sql.replace(notSpaceError.replace("<<此处缺空格>>", ""), notSpaceError.replace("<<此处缺空格>>", " "));
        notSpaceError = notSpaceAfterEvenQuotes(sql);
        while (notSpaceError != '' && sql != sql2){
            console.log("补充空格:\r\n" + notSpaceError);
            sql2 = sql;
            sql = sql.replace(notSpaceError.replace("<<此处缺空格>>", ""), notSpaceError.replace("<<此处缺空格>>", " "));
            notSpaceError = notSpaceAfterEvenQuotes(sql);
        }
        sql_editor.setValue(sql);
    }

    // 实验行功能, 检查是否缺少逗号   "select 1 as a 2 as b" 报错 "select 1 as a, 2 as b" 不报错  "select 1 as b from (select 2 as b)" 不报错
    var errorAs = null;
    var sqlSplit = removeSqlComments(sql).split(new RegExp("(\\s*;\\s*|\\s+from\\s+|[(\\s]select\\s+|\\s+where\\s+|\\s+skip\\s+|\\s+limit\\s+|\\s+group\\s+by\\s+|\\s+order\\s+by\\s+|\\s+having\\s+)","im"));
    for (var i = 0; i < sqlSplit.length; i++) {
        errorAs = sqlSplit[i].replace(/[a-zA-Z0-9_]+\([^()]*\)/gi, " x").replace(/[a-zA-Z0-9_]+\([^()]*\)/gi, " x").match(new RegExp("\\S+\\s+as\\s+([^,\\s]+\\s+)+\\S+\\s+as\\s+\\S+","im"));
        if (errorAs && errorAs.length > 0){
            break;
        }
    }
    if (errorAs && errorAs.length > 0){
        var errorAsSql = errorAs[0];
        if (errorAsSql.length  < 128) {
        var choice = window.confirm("当前sql可能缺少逗号, 是否继续查询当前选中内容: \r\n"  + (errorAsSql.length > 256 ? errorAsSql.substr(0,256) + "..." : errorAsSql));
            if (!choice) {
                return;
            }
        }
    }

    var sqlLow = sql.toLowerCase();
    // 有 select 和 from 没有 @ { [ ( where skip limit group having
    if (sqlLow.indexOf("select") == 0 && sqlLow.indexOf("from") > 0 && sqlLow.indexOf("@") < 0 && sqlLow.indexOf("{") < 0 && sqlLow.indexOf("[") < 0 && sqlLow.indexOf("(") < 0&& sqlLow.indexOf("where") < 0 && sqlLow.indexOf("skip") < 0 && sqlLow.indexOf("limit") < 0 && sqlLow.indexOf("group") < 0 && sqlLow.indexOf("having") < 0
        && confirm("sql中没有limit可能会返回大量结果,您真的确定要继续执行吗？\n\n请确认！")==false){
        return;
    }

    // 防止双击触发单击
    sybn_time = setTimeout(function() {
        var statusKey = "select_v2_" + new Date().getTime() + "_" + hashCode(sql)
        $input_group.find(".interrupt").show();
        $input_group.data("statusKey", statusKey);
        if($this.hasClass("mergeHead")){
            apiParam = "&_merge_head=true" + apiParam
        }
        if($this.hasClass("enable_stream")){
            apiParam = "&_enable_stream=true" + apiParam
        }
        if ($('.dbSource').find('option:selected').val()){
            apiParam = "&dbsource=" + $('.dbSource').find('option:selected').val() + apiParam
        } else if ($('.dbSource').val()){
            apiParam = "&dbsource=" + $('.dbSource').val() + apiParam
        }
        if (sybnSqlEngine){
            apiParam = "&sybnSqlEngine=" + sybnSqlEngine + apiParam
        }
        if (sqlMaxLength){
            apiParam = "&sqlResultMaxLength=" + sqlMaxLength + apiParam
        }
        apiParam = "&statusKey=" + statusKey + "&user=" + def_username + apiParam;

        // 替换地址栏的url, 方便复制
        var url2 = window.location.href.toString()
        if($this.hasClass("char")){
            url2 = changeURLPar(url2, "_result_view", getViewParam($panel_body));
        }
        url2 = changeURLPar(url2, "zip", zip(sql));
        window.history.pushState({},0,url2);

        if($this.hasClass("excel")){
            showLogInfo($this, statusKey);
            sqlWindowOpenFun(api, ".xlsx", sql, apiParam);
        } else if($this.hasClass("csv")){
            showLogInfo($this, statusKey);
            sqlWindowOpenFun(api, ".csv", sql, apiParam);
        } else if($this.hasClass("debug")){
            openApi(_this, e, api + ".json?_is_debug=true" + apiParam, {sql:sql});
        } else if($this.hasClass("char")){
            var view = getViewParam($panel_body);
            var url = api + ".json?sql=" + encodeURIComponent(sql) + "&_result_view=" + view + apiParam;
            $panel_body.find(".char_view").find(".chars_preview").remove();
            $panel_body.find(".char_view").append("<div class='chars_preview clear_both' id='container' style='height:300px;width:100%'></div>");
            var chartsType = $panel_body.find('[name=result_view_charts_type]').val()
            showCharts(url, $panel_body.find(".chars_preview"), chartsType);
        } else {
            openApi(_this, e, api + ".json?" + apiParam, {sql:sql})
        }
    }, 300);
};


var showLogInfo = function($this, statusKey) {
    var $p1 = $this.closest(".input-group");
    var $p2 = $p1.parent();
    if ($p1.length == 0) {
     $p1 = $clickthis.closest(".sybn-api-demo");
     $p2 = $p1;
    }
    $p2.find('.view_div2').remove();
    $p2.append("<div class='view_div bgfff view_div2'></div>")
    var $view_div = $p2.find('.view_div2');
    $view_div.append("<div class='json_view json_view2'><b>loading ...</div></div>")
    var this_interval;
    if (statusKey) {
        // 开始在状态栏更新任务进度
        this_interval = refreshSelectMessage(statusKey, $view_div, 3000, "新窗口导出, ");
    }
}

/**
 * 清理SQL中的注释 /* *\/ 和 --
 * @param sql 需要清理的SQL字符串
 * @returns 清理注释后的SQL字符串
 */
var removeSqlComments = function(sql) {
    // 先处理多行注释 /* */
    var multiLineCommentRegex = /\/\*[\s\S]*?\*\//g;
    sql = sql.replace(multiLineCommentRegex, '');

    // 再处理单行注释 --
    var singleLineCommentRegex = /--.*$/gm;
    sql = sql.replace(singleLineCommentRegex, '');
    return sql;
}

/**
 * 如果当前内容可能是 mybatis 代码, 提示是否为变量自动添加 <bind name='...' value='...' />
 */
var appendMybatisBindTags = function(sql_editor, sql) {
    if (sql.indexOf("function run() {") >= 0) {
        return false;
    } if (sql.indexOf("</") <= 0 || !(sql.indexOf("${") > 0 || sql.indexOf("#{") > 0) || (sql_editor.getSelection() && sql_editor.getSelection() != sql_editor.getValue())) {
        return false;
    }

    // 特殊情况 <foreach collection="cityIdList" open="(" close=")" separator="," item="id">#{id}</foreach> 应当取 cityIdList 而不是 id 且 去重并生成 <bind> 标签 时使用 <bind name="${name}" value="{'1', '2', '3'}"/>
    // 提取 <foreach collection="xxx"> 中的 collection，并排除 item 的占位符
    var foreachRegex = /<foreach\b[^>]*\bcollection\s*=\s*(['"])(.*?)\1[^>]*\bitem\s*=\s*(['"])(.*?)\3[^>]*>([\s\S]*?)<\/foreach\s*>/gi;
    var foreachMatch;
    var tempSql = sql; // 用于临时替换的 sql
    var tempReplacements = []; // 存储替换回来时使用的标记和原内容

    var placeholdersList = [];
    while ((foreachMatch = foreachRegex.exec(sql)) !== null) {
        var collectionName = foreachMatch[2];
        var itemName = foreachMatch[4];
        var content = foreachMatch[5];

        // 添加 collection 到 placeholders
        if (collectionName) {
           placeholdersList.push(collectionName);
        }

        // 查找 #{itemName} 并做临时替换，防止被后续正则匹配到
        var placeholderRegex = new RegExp(`(?<![#$@])[#$]\{\\s*${itemName}\\s*\}`, 'g');
        var replacementKey = `__TEMP_FOREACH_PLACEHOLDER_${tempReplacements.length}__`;
        tempReplacements.push({ key: replacementKey, original: `#{${itemName}}` });
        tempSql = tempSql.replace(placeholderRegex, replacementKey);
    }
    placeholdersList = placeholdersList
        // 去重
        .filter((item, index, arr) => arr.indexOf(item) === index)
        // 去除已经存在的 <bind name="${name}" 的变量
        .filter(name => !sql.match(new RegExp(`<bind\\s+name\\s*=\\s*(['"])${name}\\1`, 'i')))
        .filter(name => name !== 'exec_batch_error_msg');

    // 查找所有的 ${xxx} 和 #{xxx} 占位符
    var placeholders = [];
    var regex = /(?<![#$@])[#$]\{([a-zA-Z0-9_]+)\}/g;
    var match;
    while ((match = regex.exec(tempSql)) !== null) {
        if (match[1]) {
            placeholders.push(match[1]);
        }
    }
    placeholders = placeholders
        // 去重
        .filter((item, index, arr) => arr.indexOf(item) === index)
        // 去除已经存在的 <bind name="${name}" 的变量
        .filter(name => !sql.match(new RegExp(`<bind\\s+name\\s*=\\s*(['"])${name}\\1`, 'i')))
        .filter(name => name !== 'exec_batch_error_msg');

    if (placeholders.length == 0 && placeholdersList == 0) {
        return false;
    }
    // 生成提示信息
    var combined = [...new Set([...placeholders, ...placeholdersList])];
    var displayedValues = combined.slice(0, 5).join("\n"); // 显示前5个值
    if (combined.length > 5) {
        displayedValues += "\n...";
    }
    var choice = window.confirm("当前内容可能是 mybatis 代码 \n是否为以下变量自动添加 <bind name='...' value='...' /> ?\n" + displayedValues);
    if (choice) {


        // 去重并生成 <bind> 标签
        var bindTags = placeholders.map(name => `<bind name="${name}" value="''"/>`).join("\n");
        bindTags += "\n" + placeholdersList.map(name => `<bind name="${name}" value="{'1', '2', '3'}"/>`).join("\n");

        // 判断选中代码是否以 -- 开头
        if (sql.match(/<(select|update|insert|delete)\b/i)) {
            // 如果包含 <select <update <insert <delete 等标签
            // 将 bindTags 放到第一个匹配标签的 > 之后
            sql = sql.replace(/(<(select|update|insert|delete)\b[^>]*>)/i, '$1/*! native sql */\n' + bindTags);
        } else if (sql.trim().startsWith("--")) {
            // 将 bindTags 插入到第二行
            var lines = sql.split("\n");
            if (lines.length > 1) {
                lines.splice(1, 0, bindTags); // 在第二行插入 bindTags
                sql = lines.join("\n");
            } else {
                sql = bindTags + "\n" + sql; // 如果只有一行，则直接拼接
            }
        } else {
            sql = bindTags + "\n" + sql;
        }
        sql_editor.setValue(sql);
        return true;
    }
    return false;
}

// 新窗口 查询/下载
var sqlDownloadFun = function(e) {
    // 防止双击触发单击
    clearTimeout(sybn_time);

    var $this = $(this);
    if (!$this.hasClass("btn")) {
        // 获取当前焦点同级的 defaults_action
        $this = $(document.activeElement).closest(".panel").find(".defaults_action");
    }
    // $(document.activeElement)

    // 主接口地址
    var api = $this.data("api") || "api/sql/select_v2";
    // 附加参数
    var apiParam = $this.data("param") || "";

    var $input_group = $this.closest(".input-group, .sybn-api-demo")
    var $panel_body = $this.closest(".sybn-api-demo");
    if ($panel_body.length == 0){
        $panel_body = $this.closest(".input-group");
    }

    var sql_editor = window[$input_group.find('[name=url],[name=sql]').data("editor")];
    var sql = $this.hasClass("selection") && sql_editor.getSelection() ? sql_editor.getSelection() : sql_editor.getValue();
    var sybnSqlEngine = $input_group.find('[name=sybnSqlEngine]').val();
    var sqlMaxLength = $input_group.find('[name=sqlMaxLength]').val();
    if (!sql) {
        return;
    }

    // 如果当前内容可能是 mybatis 代码, 提示是否为变量自动添加 <bind name='...' value='...' />
    var append = appendMybatisBindTags(sql_editor, sql);
    if (append) {
        return;
    }

    if (sql_editor.getSelection() && sql.toUpperCase().indexOf("SELECT") < 0 && sql.toUpperCase().indexOf("SHOW") <0) {
         var choice = window.confirm("当前选中的内容可能不是完整SQL, 是否继续查询当前选中内容: \r\n"  + (sql.length > 256 ? sql.substr(0,256) + "..." : sql));
         if (!choice) {
            return;
         }
    }
    if ((sql.indexOf("　") > 0 || sql.indexOf(" ") > 0)&& confirm("sql中有中文空格,是否替换为普通空格查询？\n\n请确认！")==true){
        sql = sql.replace(/ /g," ").replace(/　/g," ");
        sql_editor.setValue(sql);
    }

    // 实验行功能, 检查是否缺少逗号
    var errorAs = sql.replace(/[a-zA-Z0-9_]+\([^()]*\)/gi, " x").replace(/[a-zA-Z0-9_]+\([^()]*\)/gi, " x").match(new RegExp("\\S+\\s+as\\s+[^,]+\\s+\\S+\\s+as\\s+\\S+","im"));
    if (errorAs && errorAs.length > 0){
        var errorAsSql = errorAs[0];
        if (errorAsSql.length  < 128) {
        var choice = window.confirm("当前sql可能缺少逗号, 是否继续查询当前选中内容: \r\n"  + (errorAsSql.length > 256 ? errorAsSql.substr(0,256) + "..." : errorAsSql));
            if (!choice) {
                return;
            }
        }
    }

    var sqlLow = sql.toLowerCase();
    // 有 select 和 from 没有 @ { [ ( where skip limit group having
    if (sqlLow.indexOf("select") == 0 && sqlLow.indexOf("from") > 0 && sqlLow.indexOf("@")<0 && sqlLow.indexOf("{") <0 && sqlLow.indexOf("[")<0 && sqlLow.indexOf("(") < 0&& sqlLow.indexOf("where") < 0 && sqlLow.indexOf("skip") < 0 && sqlLow.indexOf("limit") < 0 && sqlLow.indexOf("group") < 0 && sqlLow.indexOf("having") < 0
        && confirm("sql中没有limit可能会返回大量结果,您真的确定要继续执行吗？\n\n请确认！")==false){
        return;
    }

    var statusKey = "select_v2_" + new Date().getTime() + "_" + hashCode(sql)
    $input_group.find(".interrupt").show();
    $input_group.data("statusKey", statusKey);
    if($this.hasClass("mergeHead")){
        apiParam = "&_merge_head=true" + apiParam
    }
    if($this.hasClass("enable_stream")){
        apiParam = "&_enable_stream=true" + apiParam
    }
    if ($('.dbSource').find('option:selected').val()){
        apiParam = "&dbsource=" + $('.dbSource').find('option:selected').val() + apiParam
    } else if ($('.dbSource').val()){
        apiParam = "&dbsource=" + $('.dbSource').val() + apiParam
    }
    if (sybnSqlEngine){
        apiParam = "&sybnSqlEngine=" + sybnSqlEngine + apiParam
    }
    if (sqlMaxLength){
        apiParam = "&sqlResultMaxLength=" + sqlMaxLength + apiParam
    }
    apiParam = "&statusKey=" + statusKey + "&user=" + def_username + apiParam;


    if($this.hasClass("excel")){
        sqlWindowOpenFun(api, ".xlsx", sql, apiParam);
    } else if($this.hasClass("csv")){
        sqlWindowOpenFun(api, ".csv", sql, apiParam);
    } else if($this.hasClass("debug")){
        sqlWindowOpenFun(api, ".json", sql, "_is_debug=true&" + apiParam);
    } else if($this.hasClass("char")){
        $panel_body.find(".char_view ").append("<div class='iframe_preview clear_both' id='container' style='height:300px;width:100%'></div>");
        var view = getViewParam($panel_body);
        var url = api + ".json?sql=" + encodeURIComponent(sql) + "&_result_view=" + view + apiParam;
        var chartsType = $panel_body.find('[name=result_view_charts_type]').val()
        showCharts(url, $panel_body.find( ".chars_preview"), chartsType);
    } else {
        sqlWindowOpenFun(api, ".json", sql, apiParam);
    }
}


var sqlWindowOpenFun = function(api, extName, sql, apiParam) {
    var url = api + extName + "?sql=" + encodeURIComponent(sql) + apiParam;
    if (url.length < 4096) {
        window.open(url);
        return;
    }
    postBlank(api + extName + "?" + apiParam, {sql:sql});

}
